﻿using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.Text;
using System.Configuration;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// <see cref="DataGrid"/> template which displays the value of a bound field in a <see cref="BoundLabel"/> control.
    /// </summary>
    public class BoundLabelTemplate : BoundTemplateBase
    {
        #region Fields.

        string[] displayFields;
        string displayFormat = "{0}";

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundLabelTemplate"/> class.
        /// </summary>
        public BoundLabelTemplate()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundLabelTemplate"/> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        public BoundLabelTemplate(IBindableField field, IMessageFormatter formatter)
            : base(field, formatter)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundLabelTemplate"/> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        /// <param name="skinID">The skin to apply to the controls generated by this template.</param>
        public BoundLabelTemplate(IBindableField field, IMessageFormatter formatter, string skinID)
            : base(field, formatter, skinID)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundLabelTemplate"/> class.
        /// </summary>
        /// <param name="field"><see cref="IBindableField"/> that contains a reference and meta-data for a bound business class property.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format bound values.</param>
        /// <param name="width">The width of the label used in the template.</param>
        /// <param name="cssClass">CSS Class name applied to label created by the template.</param>
        public BoundLabelTemplate(IBindableField field, IMessageFormatter formatter, int width, string cssClass)
            : base(field, formatter, width, cssClass, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundLabelTemplate"/> class.
        /// </summary>
        /// <param name="displayFields">The format which specifies how <see cref="DisplayFields"/> are concatenated and formatted.</param>
        /// <param name="displayFormat">Data fields that provide the text content.</param>
        public BoundLabelTemplate(string displayFormat, string[] displayFields)
        {
            this.displayFormat = displayFormat;
            this.displayFields = displayFields;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets optional fields of the data item that provide the text content. 
        /// The values are displayed as defined by <see cref="DisplayFormat"/>.
        /// </summary>
        public string[] DisplayFields
        {
            get { return this.displayFields; }
            set { this.displayFields = value; }
        }

        /// <summary>
        /// Gets or sets the format which specifies how <see cref="DisplayFields"/> are concatenated and formatted.
        /// </summary>
        /// <remarks>The specified value must obey the same format as the format used by <see cref="string.Format(string, object[])"/> method.</remarks>
        public string DisplayFormat
        {
            get { return this.displayFormat; }
            set { this.displayFormat = value; }
        }

        /// <summary>
        /// Gets or sets whether new line characters are displayed as BR elements.
        /// </summary>
        /// <value><b>true</b> to replace new line characters with BR elements; otherwise <b>false</b>. Default is <b>false</b>.</value>
        public bool NewLineDisplayed { get; set; }

        /// <summary>Gets or sets whether to force the usage of conversion and format for the type specified in the <see cref="BoundTemplateBase.BoundField"/> property.</summary>
        /// <value><b>false</b> indicates that format is automatically chosen based on the field value; <b>true</b> forces format type. Default is <b>false</b>.</value>
        /// <remarks>This property should be set to <b>true</b> when underlying data source returns values of different type then expected.
        /// Typically, this occurs when NUMBER type is used in database to represent integer values. In that case, even though scale is set to zero,
        /// ADO.NET uses <see cref="System.Decimal"/> to represent value in returned <see cref="System.Data.DataTable"/> objects.</remarks>
        public bool ForceBoundFieldDataTypeFormat { get; set; }

        #endregion

        #region Methods.

        Unit fixedWidth;

        /// <summary>
        /// Sets a combination of styles required to implement fixed column width.
        /// </summary>
        /// <param name="width">Width of the column.</param>
        /// <remarks>The text (field value) is truncated and suffixed with ellipsis (...) if it doesn't fit in the cell.</remarks>
        public void SetFixedColumnWidth(Unit width)
        {
            this.Style["width"] = width.ToString();
            this.Style["display"] = "inline-block";
            this.Style["overflow"] = "hidden";
            this.Style["white-space"] = "nowrap";
            this.Style["text-overflow"] = "ellipsis";
        }

        /// <summary>
        /// Defines the <see cref="DataGrid"/> that child label controls and the template belong to.
        /// </summary>
        /// <param name="container">The <b>TableCell</b> which contains the label controls instantiated by this template.</param>
        public override void InstantiateIn(Control container)
        {
            TableCell cell = container as TableCell;
            if (cell != null && this.fixedWidth != default(Unit))
                cell.Style["width"] = this.fixedWidth.ToString();

            if (RequiresSpanElement())
            {
                BoundLabel label = new BoundLabel();
                label.DataBinding += new EventHandler(this.BindDisplayValue);
                if (this.SkinID != null)
                {
                    label.SkinID = this.SkinID;
                    Page page = HttpContext.Current.Handler as Page;
                    if (page != null)
                        label.ApplyStyleSheetSkin(page);
                }

                container.Controls.Add(label);
            }
            else
            {
                LiteralControl literal = new LiteralControl();
                literal.DataBinding += new EventHandler(this.BindDisplayValue);
                container.Controls.Add(literal);
            }
        }

        private void BindDisplayValue(object sender, EventArgs e)
        {
            if (sender is LiteralControl)
            {
                LiteralControl literal = (LiteralControl)sender;
                DataGridItem currentItem = (DataGridItem)literal.NamingContainer;
                string displayVal = GetDisplayValue(currentItem.DataItem);
                if (this.NewLineDisplayed)
                    displayVal = TextUtil.ReplaceNewLine(displayVal, "<br/>");

                literal.Text = displayVal;
            }
            else
            {
                BoundLabel label = (BoundLabel)sender;
                ApplyStyle(label);
                DataGridItem currentItem = (DataGridItem)label.NamingContainer;
                string displayVal = GetDisplayValue(currentItem.DataItem);
                if (this.NewLineDisplayed)
                    displayVal = TextUtil.ReplaceNewLine(displayVal, "<br/>");

                label.Text = displayVal;
            }
        }

        private string GetDisplayValue(object dataItem)
        {
            bool hasCustomFormat = !ArrayUtil.IsNullOrEmpty(this.DisplayFields) && !string.IsNullOrEmpty(this.DisplayFormat);
            if (hasCustomFormat)
            {
                object[] values = new object[this.DisplayFields.Length];
                for (int idxField = 0; idxField < this.DisplayFields.Length; idxField++)
                {
                    object rawValue = DataBinder.Eval(dataItem, this.DisplayFields[idxField]);
                    values[idxField] = FormatValue(rawValue);
                }

                return string.Format(this.DisplayFormat, values);
            }
            else
            {
                object val = DataBinder.Eval(dataItem, this.BoundField.BindingName);
                object displayValue = (this.BoundField.CodeList == null) ? val : this.BoundField.CodeList.GetDisplayValue(val);
                return FormatValue(displayValue);
            }
        }

        private string FormatValue(object value)
        {
            string formatted;
            if (this.Formatter == null)
            {
                formatted = Convert.ToString(value);
            }
            else
            {
                if (this.ForceBoundFieldDataTypeFormat)
                    formatted = this.Formatter.Format(value, this.BoundField.DataType);
                else
                    formatted = this.Formatter.Format(value);
            }

            return formatted;
        }

        private bool RequiresSpanElement()
        {
            bool cssDefined = !string.IsNullOrEmpty(this.CssClass);
            if (cssDefined)
                return true;

            bool widthDefined = (this.Width.Value > 0);
            if (widthDefined)
                return true;

            bool styleDefined = (this.Style != null && this.Style.Count > 0);
            if (styleDefined)
                return true;

            return false;
        }

        #endregion
    }
}